package pl.edu.agh.gronoMining.frequentSubgraphFinder.frequentSearcher.subgraph.impl;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.apache.log4j.Logger;

import pl.edu.agh.gronoMining.frequentSubgraphFinder.frequentSearcher.subgraph.DFSCode;
import pl.edu.agh.gronoMining.frequentSubgraphFinder.frequentSearcher.subgraph.StartNodeSubgraph;
import pl.edu.agh.gronoMining.frequentSubgraphFinder.frequentSearcher.subgraph.SubgraphMove;
import pl.edu.agh.gronoMining.frequentSubgraphFinder.graph.Edge;
import pl.edu.agh.gronoMining.frequentSubgraphFinder.graph.GraphException;
import pl.edu.agh.gronoMining.frequentSubgraphFinder.graph.Node;
import pl.edu.agh.gronoMining.frequentSubgraphFinder.strategy.Strategy;


public class StartNodeSubgraphImpl implements StartNodeSubgraph {
	
	/** stack of actions made by this subgraph */
	private Stack<SubgraphAction> actionsStack = new Stack<SubgraphAction>();
	
	/** map of all edge markesrs of this subgraph*/
	private Map<Edge, EdgeMarker> egesMarkers = new HashMap<Edge, EdgeMarker>();

	/** set of all current active instances */
	private Set<StartNodeInstance> activeInstances = new HashSet<StartNodeInstance>();
	
	/** index id of instances of this subgraph */
	private int instancesIdIndex = 0;
	
	/** true if this subgraph is active */
	private boolean isActive = false;
	
	private static Logger logger = Logger.getLogger(StartNodeSubgraphImpl.class);
	
	/**
	 * @param startNode start node of this subgraph
	 * @param strategy strategy object
	 */
	public StartNodeSubgraphImpl(Node startNode, Strategy strategy) {
			// create first StartNodeInstance of this subgraph, which contains only start node
		Map<Integer,Node> beginNodes = new HashMap<Integer,Node>();
		Set<Edge> beginEdges = new HashSet<Edge>();
		beginNodes.put(0, startNode);
		StartNodeInstance instance = new StartNodeInstance(beginNodes, beginEdges, 1, strategy, this.egesMarkers);
		instance.setId(instancesIdIndex++);
		instance.setIsPath(true);
		instance.setPathNode(startNode);
			// add this instance to activeInstances
		activeInstances.add(instance);
	}
	
	public void markAsActive() {
		this.isActive = true;
	}
	
	public List<SubgraphMove> getPossibleMoves(DFSCode dfs, Set<DFSCode> resultDfsSet) {
		List<SubgraphMove> resultList = new LinkedList<SubgraphMove>();
		for (StartNodeInstance instance : this.activeInstances) {
			resultList.addAll(instance.getPossibleMoves(dfs, resultDfsSet));
		}
		return resultList;
	}
	
	public boolean performMove(SubgraphMove move) throws GraphException {
			// create action object
		SubgraphAction action = new SubgraphAction(move);
		
			// create set of instances, where move was successful
		Set<StartNodeInstance> newActiveInstances = new HashSet<StartNodeInstance>();
		
			//try to perform move on all active instances
		//logger.info("performMove - "+move);		
		for (StartNodeInstance instance : this.activeInstances) {
				// try to perform move on instance
			int result = instance.performMove(move, this.isActive);
			
				// check result
			//logger.info("result = "+result);
			if (result == 0) {
				// do nothing
			} else if (result > 0) { 
				newActiveInstances.add(instance);	// add instance to newActiveInstances
				action.setNewNodeId(result);	// put id of new added node to action 
			} else {
				newActiveInstances.add(instance);	// add this instances...
				for (StartNodeInstance newInstance : instance.getNewInstances()) {	// and all new instances to newActiveInstances
					newInstance.setId(this.instancesIdIndex++);
					newActiveInstances.add(newInstance);
				}
				action.setNewNodeId(-result);	// put id of new added node to action 
			}
		}

			// check if at least one instances successfully.. 
		boolean isAtLeastOneSuccess = false;
		if (newActiveInstances.size() > 0) {
			action.setActiveInstances(this.activeInstances); // put old active instances to action object 
			this.actionsStack.push(action);	// push action to actionsStack
			this.activeInstances = newActiveInstances;	// change active instances to instances, that performed this move successfully
			isAtLeastOneSuccess = true;
		}
		
		return isAtLeastOneSuccess;
	}
	
	public void undoLastMove() throws GraphException {
			// pop last action from stack
		SubgraphAction action = this.actionsStack.pop();
		SubgraphMove move = action.getMove();
			// undo last move in all active instances
		//logger.info(" try to undo in "+this.activeInstances.size()+" instances");
		for (StartNodeInstance instance : this.activeInstances) {
			instance.undoMove(move, action.getNewNodeId(), this.isActive);
		}
			// change activeInstances to state from before this move
		this.activeInstances = action.getActiveInstances();
	}
	
	public void markLastMoveEdge(boolean isFrequent) {
		this.markLastMoveEdge(isFrequent, null);
	}
	
	public void markLastMoveEdge(boolean isFrequent, DFSCode dfs) {
			// for all edges that was last added by instances
		for (StartNodeInstance instance : this.activeInstances) {
			Edge lastEdge = instance.getLastAddedEdge();
				// get marker from of this edge, if no markers create slot for marker
			EdgeMarker marker = this.egesMarkers.get(lastEdge);
			if (marker == null) {
				marker = new EdgeMarker();
				this.egesMarkers.put(lastEdge, marker);
			}
				// mark this edge
			if (dfs == null) {
				marker.markAbsolute(isFrequent);
			} else {
				marker.addMarkerForDfs(dfs, isFrequent);
			}
		}
	}
	
	public Set<StartNodeInstance> getActiveInstances() {
		return this.activeInstances;
	}
	
	public DFSCode getAsDfs() {
		if (this.activeInstances.size() != 1) {
			logger.error("[getAsDfs] shouldn't happen");
			return null;
		}
		return this.activeInstances.iterator().next().getAsDfs();
	}
}
